/*
 * Copyright (c) 2011 The Chromium OS Authors.
 *
 * SPDX-License-Identifier:	GPL-2.0+
 */

#include <common.h>
#include <fdtdec.h>
#include <malloc.h>
#include <spi.h>
#include <linux/list.h>

static struct list_head spi_bus;

struct spi_devices
{
	struct list_head link;
	int bus_num;
	struct spi_slave *(*spi_setup_slave)(unsigned int bus, unsigned int cs,
				unsigned int max_hz, unsigned int mode);
	void *priv;
};

void spi_list_init(void)
{
	INIT_LIST_HEAD(&spi_bus);
}

void spi_list_add(struct spi_devices *spi_device)
{
	INIT_LIST_HEAD(&spi_device->link);

	list_add_tail(&spi_device->link, &spi_bus);
}

struct spi_devices *find_spi_bus(int bus_num)
{
	struct spi_devices *spi;
	struct list_head *entry;

	list_for_each(entry, &spi_bus) {
		spi = list_entry(entry, struct spi_devices, link);

		if (spi->bus_num == bus_num)
			return spi;
	}

	return NULL;
}

void spi_init(void)
{
	spi_list_init();
}

void spi_init_f(void)
{
}

void spi_bus_register(int bus_num, struct spi_slave *(*setup)(unsigned int, unsigned int, unsigned int, unsigned int))
{
	struct spi_devices *spi;

	spi = malloc(sizeof(struct spi_devices));
	if (spi == NULL)
		return;

	spi->bus_num = bus_num;
	spi->spi_setup_slave = setup;
	spi_list_add(spi);
}

struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs,
				unsigned int max_hz, unsigned int mode)
{
	struct spi_devices *spi = find_spi_bus(bus);

	if (spi && spi->spi_setup_slave)
		return spi->spi_setup_slave(bus, cs, max_hz, mode);
	else
		return NULL;
}

void spi_cs_activate(struct spi_slave *slave)
{
	if (slave->spi_cs_activate)
		slave->spi_cs_activate(slave);
}

void spi_cs_deactivate(struct spi_slave *slave)
{
	if (slave->spi_cs_deactivate)
		return slave->spi_cs_deactivate(slave);
}

void spi_free_slave(struct spi_slave *slave)
{
	if (slave->spi_free_slave)
		return slave->spi_free_slave(slave);
}

int spi_claim_bus(struct spi_slave *slave)
{
	if (slave->spi_claim_bus)
		return slave->spi_claim_bus(slave);
	else
		return -ENOTSUPP;
}

void spi_release_bus(struct spi_slave *slave)
{
	if (slave->spi_release_bus)
		return slave->spi_release_bus(slave);
}

int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout,
                void *din, unsigned long flags)
{
	if (slave->spi_xfer)
		return slave->spi_xfer(slave, bitlen, dout, din, flags);
	else
		return -ENOTSUPP;
}

int spi_set_wordlen(struct spi_slave *slave, unsigned int wordlen)
{
	if (wordlen == 0 || wordlen > 32) {
		printf("spi: invalid wordlen %d\n", wordlen);
		return -1;
	}

	slave->wordlen = wordlen;

	return 0;
}

void *spi_do_alloc_slave(int offset, int size, unsigned int bus,
			unsigned int cs)
{
	struct spi_slave *slave;
	void *ptr;

	ptr = malloc(size);
	if (ptr) {
		memset(ptr, '\0', size);
		slave = (struct spi_slave *)(ptr + offset);
		slave->bus = bus;
		slave->cs = cs;
		slave->wordlen = SPI_DEFAULT_WORDLEN;
	}

	return ptr;
}

#ifdef CONFIG_OF_SPI
struct spi_slave *spi_base_setup_slave_fdt(const void *blob, int busnum,
					int node)
{
	int cs, max_hz, mode = 0;

	cs = fdtdec_get_int(blob, node, "reg", -1);
	max_hz = fdtdec_get_int(blob, node, "spi-max-frequency", 100000);
	if (fdtdec_get_bool(blob, node, "spi-cpol"))
		mode |= SPI_CPOL;
	if (fdtdec_get_bool(blob, node, "spi-cpha"))
		mode |= SPI_CPHA;
	if (fdtdec_get_bool(blob, node, "spi-cs-high"))
		mode |= SPI_CS_HIGH;
	if (fdtdec_get_bool(blob, node, "spi-half-duplex"))
		mode |= SPI_PREAMBLE;
	return spi_setup_slave(busnum, cs, max_hz, mode);
}
#endif
